/* eslint-disable no-undef */
/* eslint-disable no-new */

class Compile {
  constructor (el, vm) {
    this.vm = vm
    this.el = document.querySelector(el)
    this.fragment = null
    this.init()
  }
  init () {
    if (this.el) {
      this.fragment = this.nodeToFragment(this.el)
      this.compileElement(this.fragment)
      this.el.appendChild(this.fragment)
    } else {
      throw new Error('element not found,Check and try again')
    }
  }
  nodeToFragment (el) {
    let fragment = document.createDocumentFragment()
    let child = el.firstChild
    while (child) {
      // 将Dom元素移入fragment中操作再挂载到DOM树，避免引起过多次的重绘和回流
      fragment.appendChild(child)
      child = el.firstChild
    }
    return fragment
  }
  compileElement (el) {
    let childNodes = el.childNodes
    Array.prototype.slice.call(childNodes).forEach((node) => {
      let reg = /\{\{\s*(.*?)\s*\}\}/
      let text = node.textContent

      if (this.isElementNode(node)) {
        this.compileAttrs(node)
      } else if (this.isTextNode(node) && reg.test(text)) {
        this.compileText(node, reg.exec(text)[1])
      }

      if (node.childNodes && node.childNodes.length) {
        this.compileElement(node)
      }
    })
  }
  compileAttrs (node) {
    let nodeAttrs = node.attributes
    Array.prototype.slice.call(nodeAttrs).forEach((attr) => {
      let attrName = attr.name
      if (this.isDirective(attrName)) {
        let exp = attr.value
        let dir = attrName.substring(2)
        if (this.isEventDirective(dir)) { // 事件指令
          this.compileEvent(node, this.vm, exp, dir)
        } else { // v-model 指令
          this.compileModel(node, this.vm, exp)
        }
        node.removeAttribute(attrName)
      } else if (attrName.startsWith('@')) {
        let exp = attr.value
        let dir = attrName
        this.compileEvent(node, this.vm, exp, dir)
        node.removeAttribute(attrName)
      }
    })
  }
  compileText (node, exp) {
    let initText = null
    initText = this.vm[exp]
    this.updateText(node, initText, node.textContent)
    new Watcher(this.vm, exp, (value, nodeContent) => {
      this.updateText(node, value, nodeContent)
    })
  }
  compileEvent (node, vm, exp, dir) {
    let eventType = ''
    if (dir.indexOf(':') !== -1) {
      eventType = dir.split(':')[1]
    } else {
      eventType = dir.substring(1)
    }
    let cb = vm.methods && vm.methods[exp]
    if (eventType && cb) {
      node.addEventListener(eventType, cb.bind(vm), false)
    }
  }
  compileModel (node, vm, exp) {
    let val = vm[exp]
    this.modelUpdater(node, val)
    new Watcher(vm, exp, (value) => {
      this.modelUpdater(node, value)
    })

    node.addEventListener('input', (e) => {
      let newValue = e.target.value
      if (val === newValue) {
        return
      }
      this.vm[exp] = newValue
      val = newValue
    })
  }
  updateText (node, value, nodeContent) {
    // 修复不能精确定位到插值括号内部变量问题
    Watcher.nodeContent = node.textContent
    if (typeof value === 'undefined') {
      node.textContent = ''
    } else {
      if (nodeContent) node.textContent = nodeContent.replace(/\{\{\s*(.*?)\s*\}\}/, value)
      else node.textContent = value
    }
  }
  modelUpdater (node, value) {
    node.value = typeof value === 'undefined' ? '' : value
  }
  isDirective (attr) {
    return attr.indexOf('v-') === 0
  }
  isEventDirective (dir) {
    return dir.indexOf('on:') === 0
  }
  isElementNode (node) {
    return node.nodeType === 1
  }
  isTextNode (node) {
    return node.nodeType === 3
  }
}

(function () {
  return Compile
})()
